#include "crt.h"
#include <math.h>
#include <fstream>
#include "SpaceImp.h"

using namespace std;


bool TCRTMesh::Optimize()
{
	vertices_m.clear();
	aabb.Init();

	for (vector<TCRTVertexFull>::iterator it = vertices_f.begin(); it != vertices_f.end(); it++)
	{
		vertices_m.push_back(it->position);
		aabb += it->position;
	}

	return true;
}

TCRTScene::TCRTScene()
{
	mpSpace = NULL;

	mnRayTriangleTest = 0;
	mnRayAABBTest = 0;
}

TCRTScene::~TCRTScene()
{
	for (vector<TCRTMesh*>::iterator it = meshes.begin(); it != meshes.end(); it++)
		delete (*it);

	meshes.empty();

	delete mpSpace;
	mpSpace = NULL;
}

bool TCRTScene::LoadSceneFromASE(TCHAR* /*filename*/)
{
    return true;
}

void TCRTScene::ResetStat()
{
	mnRayTriangleTest = 0;
	mnRayAABBTest = 0;
}

size_t  TCRTScene::GetRayTriangleTestNum()
{
	return mnRayTriangleTest;
}

size_t  TCRTScene::GetRayAABBTestNum()
{
	return mnRayAABBTest;
}

double  TCRTScene::GetRayTriangleTestTime()
{
	return 0.0;
}

bool TCRTScene::TraceRay(RAY& ray, vector<TSceneIntersection>& outHits, TraceType traceType)
{
	VEC3 center = aabb.GetCenter();
	VEC3 size = aabb.GetSize();

	float fDistance = (ray.origin - center).Length();
	float fRadius = size.Length();

	ray.sign[0] = ray.direction.x < 0 ? 1 : 0;
	ray.sign[1] = ray.direction.y < 0 ? 1 : 0;
	ray.sign[2] = ray.direction.z < 0 ? 1 : 0;

	float temp;

	temp = max(fabs(ray.direction.x), CRT_MATH_EPSILON);
	if (ray.sign[0] == 1)
	{
		temp = -temp;
	}
	ray.invDirection.x = 1.0f / temp;
	temp = max(fabs(ray.direction.y), CRT_MATH_EPSILON);
	if (ray.sign[1] == 1)
	{
		temp = -temp;
	}
	ray.invDirection.y = 1.0f / temp;	
	temp = max(fabs(ray.direction.z), CRT_MATH_EPSILON);
	if (ray.sign[2] == 1)
	{
		temp = -temp;
	}
	ray.invDirection.z = 1.0f / temp;

	ray.effectiveOrigin = ray.origin;
	ray.effectiveTarget = ray.origin + ray.direction * (fDistance + fRadius);

	outHits.clear();

	ITraceResult* pResult = NULL;
	if (NULL != mpSpace)
	{
		pResult = mpSpace->TraceRay(&ray, traceType);
		for (size_t i = 0; i < pResult->NumberOfHits(); i++)
		{
			const TSceneIntersection* pHit = pResult->GetHit(i);
			outHits.push_back(*pHit);
		}
	}

	if (pResult)
	{
		mnRayTriangleTest += pResult->NumberOfRayTriangleTesting();
		mnRayAABBTest += pResult->NumberOfRayAABBTesting();
	}

	return pResult && pResult->HitAny();
}



bool TCRTSceneCreateDXMesh(struct ID3DXMesh* /*pMesh*/);




int TCRTMesh::AddVertex(int vpos, TCRTVertexFull &v)
{
	int index = 0;
	if (vpos >= static_cast<int>(hash.size()))
	{
		index = static_cast<int>(vertices_f.size());
		vertices_f.push_back(v);

		// insert several NULL entries		
		int hashsize = static_cast<int>(hash.size());
		for (int i = 0; i < vpos - hashsize; i++)
			hash.push_back(NULL);

		// insert the new entry
		TVertexHashEntry* newentry = new TVertexHashEntry();
		newentry->next		= NULL;
		newentry->vindex	= index;
		hash.push_back(newentry);
	}
	else
	{
		bool found = false;

		// check with all the entries with the same vertex index
		TVertexHashEntry* entry = hash[vpos];
		while (entry)
		{
			TCRTVertexFull& targetv = vertices_f[entry->vindex];
			if (!memcmp(&targetv, &v, sizeof(TCRTVertexFull)))
			{
				found = true;
				index = entry->vindex;
				break;
			}
			entry = entry->next;
		}

		if (!found)
		{
			index = static_cast<int>(vertices_f.size());
			vertices_f.push_back( v );

			// create a new entry, link to the hash table
			TVertexHashEntry* newentry = new TVertexHashEntry();
			newentry->next		= hash[vpos];
			newentry->vindex	= index;
			
			hash[vpos] = newentry;
		}
	}

	return index;
}


TCRTMesh::TCRTMesh()
{
	vertices_f.clear();
	vertices_m.clear();
	triangles.clear();
	triangleHelpers.clear();
	hash.clear();

	wcscpy(name, L"NoName");
}

TCRTMesh::~TCRTMesh()
{
	ClearCache();
}

void TCRTMesh::ClearCache()
{
	for (vector<TVertexHashEntry*>::iterator it = hash.begin(); it != hash.end(); it++)
	{
		TVertexHashEntry* entry = *it;
		while (entry)
		{
			TVertexHashEntry* tmp = entry->next;
			delete entry;
			entry = tmp;
		}
	}

	hash.clear();
}

bool TCRTScene::LoadSceneFromOBJ(TCHAR* filename)
{
	meshes.clear();
	lights.clear();

    // Create temporary storage for the input data. Once the data has been loaded into
    // a reasonable format we can create a D3DXMesh object and load it with the mesh data.
    vector<VEC3> Positions;
    vector<VEC2> TexCoords;
    vector<VEC3> Normals;

    // File input
    WCHAR strCommand[256] = {0};
    wifstream InFile( filename );
    if( !InFile )
        return false;

	bool NewGroup = false;
	TCRTMesh* NewMesh = NULL;
    
    for(;;)
    {
        InFile >> strCommand;
        if( !InFile )
            break;
        
        if( 0 == wcscmp( strCommand, L"#" ) )
        {
            // Comment
        }
        else if( 0 == wcscmp( strCommand, L"v" ) )
        {
            // Vertex Position
			VEC3	v;
            InFile >> v.x >> v.y >> v.z;
			Positions.push_back( v ); 
        }
        else if( 0 == wcscmp( strCommand, L"vt" ) )
        {
            // Vertex TexCoord
            VEC2	v;
            InFile >> v.x >> v.y;
            TexCoords.push_back( v );
        }
        else if( 0 == wcscmp( strCommand, L"vn" ) )
        {
            // Vertex Normal
			VEC3	v;
            InFile >> v.x >> v.y >> v.z;
			v.Normalize();
			Normals.push_back( v );
        }
		else if( 0 == wcscmp( strCommand, L"g" ) )
		{
			// Push the previous mesh into mesh array
			if (NewMesh && NewMesh->triangles.size())
			{
				NewMesh->ClearCache();
				meshes.push_back(NewMesh);
			}
			else
			{
				delete NewMesh;
			}

			// New group
			NewGroup = true;

			// Create the mesh skeleton
			NewMesh = new TCRTMesh();
			NewMesh->meshID = int(meshes.size());

			InFile >> NewMesh->name;

			// Assign randomized material to this mesh
			TCRTMaterial& material = NewMesh->material;

			material.specular.Randomize();

			material.specular = COLOR(material.specular.r / 2 + 128, 
									material.specular.g / 2 + 128,
									material.specular.b / 2 + 128,
									255);

			//material.specular= COLOR(255,255,255);
			material.ambient	= material.specular * 0.2f;
			material.diffuse	= material.specular * 0.8f;
			material.shininess	= 15.0f;
		}
        else if( 0 == wcscmp( strCommand, L"f" ) )
        {
			NewGroup = true;

            // Face
            UINT iPosition, iTexCoord, iNormal;
            TCRTVertexFull	vertex;
			TCRTTriangle	triangle;

            for( UINT iFace=0; iFace < 3; iFace++ )
            {
                ZeroMemory( &vertex, sizeof(TCRTVertexFull) );

                // OBJ format uses 1-based arrays
                InFile >> iPosition;
                vertex.position = Positions[ iPosition-1 ];

                if( '/' == InFile.peek() )
                {
                    InFile.ignore();

                    if( '/' != InFile.peek() )
                    {
                        // Optional texture coordinate
                        InFile >> iTexCoord;
                        vertex.texcoord = TexCoords[ iTexCoord-1 ];
                    }

                    if( '/' == InFile.peek() )
                    {
                        InFile.ignore();

                        // Optional vertex normal
                        InFile >> iNormal;
                        vertex.normal = Normals[ iNormal-1 ];
                    }
                }
				triangle.index[iFace] = NewMesh->AddVertex(iPosition, vertex);
            }
			NewMesh->triangles.push_back(triangle);
        }
        else
        {
            // Unimplemented or unrecognized command
        }

        InFile.ignore( 1000, '\n' );
    }

	// The last unsaved mesh
	if (NewMesh && NewMesh->triangles.size())
	{
		NewMesh->ClearCache();
		meshes.push_back(NewMesh);
	}
	else
	{
		delete NewMesh;
	}

	// Create the triangle helper data, see the comments for TCRTTriangleHelper in crt.h
	for (vector<TCRTMesh*>::iterator it = meshes.begin(); it < meshes.end(); it++)
	{
		TCRTMesh& mesh = *(*it);
		mesh.triangleHelpers.resize(mesh.triangles.size());
		for (size_t i = 0; i < mesh.triangles.size(); i++)
		{
			TCRTTriangle& t = mesh.triangles[i];
			TCRTTriangleHelper& h = mesh.triangleHelpers[i];

			VEC3& v0 = mesh.vertices_f[t.index[0]].position;
			VEC3& v1 = mesh.vertices_f[t.index[1]].position;
			VEC3& v2 = mesh.vertices_f[t.index[2]].position;

			VEC3 V1 = v1 - v0;
			VEC3 V2 = v2 - v0;
			VEC3 nV1 = V1; nV1.Normalize();
			VEC3 nV2 = V2; nV2.Normalize();
			float a1, a2;

			h.plane.Normal		= nV1.CrossR(nV2);
			h.plane.Normal.Normalize();
			h.plane.Distance	= -h.plane.Normal.Dot(v0);
			h.perp1 = V2 - nV1 * (V2.Dot(nV1)); h.perp1.Normalize();
			h.perp2 = V1 - nV2 * (V1.Dot(nV2)); h.perp2.Normalize();

			a1 = 1.0f / (V1.Dot(h.perp2));
			h.b1 = - v0.Dot(h.perp2) / (V1.Dot(h.perp2));
			a2 = 1.0f / (V2.Dot(h.perp1));
			h.b2 = - v0.Dot(h.perp1) / (V2.Dot(h.perp1));

			h.perp1 *= a2;
			h.perp2 *= a1;

			h.nextTestedHelper = NULL;
		}
	}

	// Cleanup
    InFile.close();

    return true;
}


bool TCRTScene::Optimize()
{
	for (vector<TCRTMesh*>::iterator it = meshes.begin(); it != meshes.end(); it++)
	{
		if (!(*it)->Optimize())
			return false;
	}

	aabb.Init();
	for (vector<TCRTMesh*>::iterator it = meshes.begin(); it != meshes.end(); it++)
		aabb += (*it)->aabb;

	// Scene management
	if (NULL == mpSpace)
	{
		mpSpace = new SpaceImp();
	}

	mpSpace->ClearAllMeshes();
	for (size_t i = 0; i < meshes.size(); i++)
	{
		mpSpace->AddMesh(meshes[i]);
	}

	if (!mpSpace->BuildPartition())
	{
		mpSpace->ClearAllMeshes();
		return false;
	}

	// Manually add one light
	lights.clear();

	TCRTLight light;
	light.type		= CRT_LT_DIRECTIONAL;
	light.direction = VEC3(0, -1, 0);
	light.color		= VEC3(1.0f, 1.0f, 1.0f);

	lights.push_back( light );

	return true;
}


bool TCRTOutput::SaveToTGA(TCHAR* /*filename*/)
{
	return true;
}

//
// check the output setting : 
//			image size
//			buffer size
//
bool TCRTOutput::Check()
{
	if (imagew > CRT_MAX_OUTPUT_WIDTH || imagew == 0 ||
		imageh > CRT_MAX_OUTPUT_HEIGHT || imageh == 0)
		return false;

	if (!buffer)
		return false;

	return true;
}

// check the camera setting
bool TCRTCamera::Check()
{
	return true;
}

// check the CRT scene
bool TCRTScene::Check()
{
	if (meshes.size() == 0)
		return false;

	if (NULL == mpSpace)
	{
		return false;
	}
		
	return true;
}

int TCRTScene::GetTriangleCount()
{
	int result = 0;

	for (vector<TCRTMesh*>::iterator it = meshes.begin(); it != meshes.end(); it++)
	{
		result += static_cast<int>((*it)->triangles.size());
	}

	return result;
}

int TCRTScene::GetVertexCount()
{
	int result = 0;

	for (vector<TCRTMesh*>::iterator it = meshes.begin(); it != meshes.end(); it++)
	{
		result += static_cast<int>((*it)->vertices_f.size());
	}

	return result;
}